package Worker;

/**
 * Project:    metsysdirg
 * File Name: WorkerEngine.java
 * Change History:
 *     ********************************
 *     1.0     5/14/2011    Hengfeng Li
 *         Create the skeleton file
 *     ********************************
 *
 */
import Module.ResultUnit;
import Module.StatusService;
import Module.WorkService;
import Module.WorkUnit;

import java.io.*;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.management.timer.TimerMBean;

/**
 * Class WorkerEngine. Provides the implementation of
 * all the services in a worker.
 */
public class WorkerEngine extends UnicastRemoteObject
            implements WorkService{
    private RegistryInfo registryInfo;
    private StatusInfo statusInfo;
    private Process process;
    private boolean isCancel;

    /**
     * Initializes an instance of the WorkerEngine class.
     * @throws RemoteException
     */
    public WorkerEngine(RegistryInfo registryInfo,
                        StatusInfo statusInfo) throws RemoteException {
        super();

        this.registryInfo = registryInfo;
        this.statusInfo = statusInfo;
        // active worker
        this.statusInfo.setWorkerStatus(StatusService.WorkerStatus.active);

    }

    /**
     * Executes the work unit from the master-side
     * @param workUnit a work unit including the info of program
     * @return
     * @throws RemoteException
     */
    public ResultUnit execute(WorkUnit workUnit, String authCode) throws RemoteException{



        // verify the authentication code
        if(!registryInfo.verifyAuthCode(authCode)){
            System.out.println("authCode: " + authCode);
            System.out.println("authentication fail");
            // authentication fail
            return null;
        }

        int jobID = workUnit.getWorkUnitID();
        this.statusInfo.setWorkerStatus(StatusService.WorkerStatus.running);
        this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.parepare);

        /*
         I think at the basic version, we just output the result of
         the program to local file in worker-side.
         */
        byte[] programBody = workUnit.getProgramBody();

        byte[] datafile = workUnit.getDatafile();

        byte[] buffer = null;

        String command;

        long   startTime = 0, endTime = 0;
        //String currentPath = System.getProperty("user.dir") + "/";
        //String currentOS = System.getProperty("os.name");

        //int readNum = 0;

        try {
            String program = workUnit.getProgramName();
            // write program into worker temporarily
            IO.writeFile(program,programBody);
            // write data file into worker temporarily
            IO.writeFile(workUnit.getDatafileName(), datafile);

            this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.ready);
            /*
            if(currentOS.compareTo("Mac OS X") == 0){
                // running on Mac OS X, the change file permission
                System.out.println("change permission");
                command = "chmod 777 " + currentPath + program + " "
                        + currentPath + workUnit.getDatafileName();

                process = Runtime.getRuntime().exec(command);

                process.waitFor();

                if(process.exitValue() != 0) {
                    // the process didn't finish and exit,
                    // it will set the info of StatusService
                    // and let the Master know it.
                }
            } */

            command = makeACommand(program ,workUnit.getArguments());
            System.out.println(">> Cmd: "+command);

            this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.running);

            startTime = System.currentTimeMillis(); 
            // start the command
            process = Runtime.getRuntime().exec(command);

            
            // get command's output stream and
            // put a buffered read input stream on it
            //InputStream inputStream = process.getInputStream();

            //BufferedReader br =
            //        new BufferedReader(new InputStreamReader(inputStream));
            //readNum = inputStream.read(buffer);

            //String str;
            //while((str = br.readLine()) != null) {
            //    System.out.println(str);
            //}

            // wait for command to terminate
            process.waitFor();
            
            endTime = System.currentTimeMillis(); 
            

            // check its exit value;
            if(process.exitValue() != 0) {
                // the process didn't finish and exit,
                // it will set the info of StatusService
                // and let the Master know it.
            	  	this.statusInfo.setWorkerStatus(StatusService.WorkerStatus.active);
            	if(this.isCancel==false){
            		this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.failed);
            	}
            	else{
            		this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.cancel);
            	}
                //System.out.println("exitValue=0");
                System.out.println("Error happens while program exits.");
                return null;
            }else{
            	
            	System.out.println("exitValue=0");
            }


            //inputStream.close();

            // read the output file into an array of bytes
            buffer = IO.readFile(workUnit.getOutputFileName());

            // after running the program, clean files in worker
            // IO.deleteFile(program);
            // IO.deleteFile(workUnit.getDatafileName());
            // IO.deleteFile(workUnit.getOutputFileName());
            this.statusInfo.setWorkerStatus(StatusService.WorkerStatus.active);
            this.statusInfo.setJobStatus(jobID,StatusService.JobStatus.done);

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        //System.out.println("read number: " + readNum);
        //byte [] newbuffer = new byte[readNum];
        //System.arraycopy(buffer,0, newbuffer,0,readNum);

        TaskResultUnit taskResultUnit = new TaskResultUnit(buffer);
        float totalTime = (endTime - startTime) /1000.0f;
        System.out.println("totalTime = " + totalTime);
        taskResultUnit.setElapsedTime(totalTime);
        taskResultUnit.setTotalCost(totalTime * registryInfo.getCost());
        return taskResultUnit;
    }

    /**
     * Transform program name and arguments to a command
     * @param program program name
     * @param arguments arugments
     * @return a command will be running
     */
    private String makeACommand(String program, String[] arguments) {

        //System.out.println(System.getProperty("user.dir"));
        //System.out.println(System.getProperty("os.name"));
        StringBuilder runCommand = new StringBuilder("java ");

        int index = program.lastIndexOf('.');

        String str = program.substring(0, index);
        
        runCommand.append(str);

        /* Reconstruct the arguments into a string */
        for(String argument : arguments) {
            runCommand.append(" ");
            runCommand.append(argument);
        }

        return runCommand.toString();
    }

	public void cancelJob(String authCode) throws RemoteException {
			this.isCancel=true;
			this.process.destroy();
	}
	
	
	
}
